// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/common_decoder.h"

#include "base/numerics/safe_math.h"
#include "gpu/command_buffer/service/cmd_buffer_engine.h"

namespace gpu {

const CommonDecoder::CommandInfo CommonDecoder::command_info[] = {
#define COMMON_COMMAND_BUFFER_CMD_OP(name)                  \
    {                                                       \
        &CommonDecoder::Handle##name,                       \
        cmd::name::kArgFlags,                               \
        cmd::name::cmd_flags,                               \
        sizeof(cmd::name) / sizeof(CommandBufferEntry) - 1, \
    }, /* NOLINT */
    COMMON_COMMAND_BUFFER_CMDS(COMMON_COMMAND_BUFFER_CMD_OP)
#undef COMMON_COMMAND_BUFFER_CMD_OP
};

CommonDecoder::Bucket::Bucket()
    : size_(0)
{
}

CommonDecoder::Bucket::~Bucket() { }

void* CommonDecoder::Bucket::GetData(size_t offset, size_t size) const
{
    if (OffsetSizeValid(offset, size)) {
        return data_.get() + offset;
    }
    return NULL;
}

void CommonDecoder::Bucket::SetSize(size_t size)
{
    if (size != size_) {
        data_.reset(size ? new int8[size] : NULL);
        size_ = size;
        memset(data_.get(), 0, size);
    }
}

bool CommonDecoder::Bucket::SetData(
    const void* src, size_t offset, size_t size)
{
    if (OffsetSizeValid(offset, size)) {
        memcpy(data_.get() + offset, src, size);
        return true;
    }
    return false;
}

void CommonDecoder::Bucket::SetFromString(const char* str)
{
    // Strings are passed NULL terminated to distinguish between empty string
    // and no string.
    if (!str) {
        SetSize(0);
    } else {
        size_t size = strlen(str) + 1;
        SetSize(size);
        SetData(str, 0, size);
    }
}

bool CommonDecoder::Bucket::GetAsString(std::string* str)
{
    DCHECK(str);
    if (size_ == 0) {
        return false;
    }
    str->assign(GetDataAs<const char*>(0, size_ - 1), size_ - 1);
    return true;
}

bool CommonDecoder::Bucket::GetAsStrings(
    GLsizei* _count, std::vector<char*>* _string, std::vector<GLint>* _length)
{
    const size_t kMinBucketSize = sizeof(GLint);
    // Each string has at least |length| in the header and a NUL character.
    const size_t kMinStringSize = sizeof(GLint) + 1;
    const size_t bucket_size = this->size();
    if (bucket_size < kMinBucketSize) {
        return false;
    }
    char* bucket_data = this->GetDataAs<char*>(0, bucket_size);
    GLint* header = reinterpret_cast<GLint*>(bucket_data);
    GLsizei count = static_cast<GLsizei>(header[0]);
    if (count < 0) {
        return false;
    }
    const size_t max_count = (bucket_size - kMinBucketSize) / kMinStringSize;
    if (max_count < static_cast<size_t>(count)) {
        return false;
    }
    GLint* length = header + 1;
    std::vector<char*> strs(count);
    base::CheckedNumeric<size_t> total_size = sizeof(GLint);
    total_size *= count + 1; // Header size.
    if (!total_size.IsValid())
        return false;
    for (GLsizei ii = 0; ii < count; ++ii) {
        strs[ii] = bucket_data + total_size.ValueOrDefault(0);
        total_size += length[ii];
        total_size += 1; // NUL char at the end of each char array.
        if (!total_size.IsValid() || total_size.ValueOrDefault(0) > bucket_size || strs[ii][length[ii]] != 0) {
            return false;
        }
    }
    if (total_size.ValueOrDefault(0) != bucket_size) {
        return false;
    }
    DCHECK(_count && _string && _length);
    *_count = count;
    *_string = strs;
    _length->resize(count);
    for (GLsizei ii = 0; ii < count; ++ii) {
        (*_length)[ii] = length[ii];
    }
    return true;
}

CommonDecoder::CommonDecoder()
    : engine_(NULL)
{
}

CommonDecoder::~CommonDecoder() { }

void* CommonDecoder::GetAddressAndCheckSize(unsigned int shm_id,
    unsigned int data_offset,
    unsigned int data_size)
{
    CHECK(engine_);
    scoped_refptr<gpu::Buffer> buffer = engine_->GetSharedMemoryBuffer(shm_id);
    if (!buffer.get())
        return NULL;
    return buffer->GetDataAddress(data_offset, data_size);
}

scoped_refptr<gpu::Buffer> CommonDecoder::GetSharedMemoryBuffer(
    unsigned int shm_id)
{
    return engine_->GetSharedMemoryBuffer(shm_id);
}

const char* CommonDecoder::GetCommonCommandName(
    cmd::CommandId command_id) const
{
    return cmd::GetCommandName(command_id);
}

CommonDecoder::Bucket* CommonDecoder::GetBucket(uint32 bucket_id) const
{
    BucketMap::const_iterator iter(buckets_.find(bucket_id));
    return iter != buckets_.end() ? &(*iter->second) : NULL;
}

CommonDecoder::Bucket* CommonDecoder::CreateBucket(uint32 bucket_id)
{
    Bucket* bucket = GetBucket(bucket_id);
    if (!bucket) {
        bucket = new Bucket();
        buckets_[bucket_id] = linked_ptr<Bucket>(bucket);
    }
    return bucket;
}

namespace {

    // Returns the address of the first byte after a struct.
    template <typename T>
    const void* AddressAfterStruct(const T& pod)
    {
        return reinterpret_cast<const uint8*>(&pod) + sizeof(pod);
    }

    // Returns the address of the frst byte after the struct.
    template <typename RETURN_TYPE, typename COMMAND_TYPE>
    RETURN_TYPE GetImmediateDataAs(const COMMAND_TYPE& pod)
    {
        return static_cast<RETURN_TYPE>(const_cast<void*>(AddressAfterStruct(pod)));
    }

} // anonymous namespace.

// Decode command with its arguments, and call the corresponding method.
// Note: args is a pointer to the command buffer. As such, it could be changed
// by a (malicious) client at any time, so if validation has to happen, it
// should operate on a copy of them.
error::Error CommonDecoder::DoCommonCommand(
    unsigned int command,
    unsigned int arg_count,
    const void* cmd_data)
{
    if (command < arraysize(command_info)) {
        const CommandInfo& info = command_info[command];
        unsigned int info_arg_count = static_cast<unsigned int>(info.arg_count);
        if ((info.arg_flags == cmd::kFixed && arg_count == info_arg_count) || (info.arg_flags == cmd::kAtLeastN && arg_count >= info_arg_count)) {
            uint32 immediate_data_size = (arg_count - info_arg_count) * sizeof(CommandBufferEntry); // NOLINT
            return (this->*info.cmd_handler)(immediate_data_size, cmd_data);
        } else {
            return error::kInvalidArguments;
        }
    }
    return error::kUnknownCommand;
}

error::Error CommonDecoder::HandleNoop(
    uint32 immediate_data_size,
    const void* cmd_data)
{
    return error::kNoError;
}

error::Error CommonDecoder::HandleSetToken(
    uint32 immediate_data_size,
    const void* cmd_data)
{
    const cmd::SetToken& args = *static_cast<const cmd::SetToken*>(cmd_data);
    engine_->set_token(args.token);
    return error::kNoError;
}

error::Error CommonDecoder::HandleSetBucketSize(
    uint32 immediate_data_size,
    const void* cmd_data)
{
    const cmd::SetBucketSize& args = *static_cast<const cmd::SetBucketSize*>(cmd_data);
    uint32 bucket_id = args.bucket_id;
    uint32 size = args.size;

    Bucket* bucket = CreateBucket(bucket_id);
    bucket->SetSize(size);
    return error::kNoError;
}

error::Error CommonDecoder::HandleSetBucketData(
    uint32 immediate_data_size,
    const void* cmd_data)
{
    const cmd::SetBucketData& args = *static_cast<const cmd::SetBucketData*>(cmd_data);
    uint32 bucket_id = args.bucket_id;
    uint32 offset = args.offset;
    uint32 size = args.size;
    const void* data = GetSharedMemoryAs<const void*>(
        args.shared_memory_id, args.shared_memory_offset, size);
    if (!data) {
        return error::kInvalidArguments;
    }
    Bucket* bucket = GetBucket(bucket_id);
    if (!bucket) {
        return error::kInvalidArguments;
    }
    if (!bucket->SetData(data, offset, size)) {
        return error::kInvalidArguments;
    }

    return error::kNoError;
}

error::Error CommonDecoder::HandleSetBucketDataImmediate(
    uint32 immediate_data_size,
    const void* cmd_data)
{
    const cmd::SetBucketDataImmediate& args = *static_cast<const cmd::SetBucketDataImmediate*>(cmd_data);
    const void* data = GetImmediateDataAs<const void*>(args);
    uint32 bucket_id = args.bucket_id;
    uint32 offset = args.offset;
    uint32 size = args.size;
    if (size > immediate_data_size) {
        return error::kInvalidArguments;
    }
    Bucket* bucket = GetBucket(bucket_id);
    if (!bucket) {
        return error::kInvalidArguments;
    }
    if (!bucket->SetData(data, offset, size)) {
        return error::kInvalidArguments;
    }
    return error::kNoError;
}

error::Error CommonDecoder::HandleGetBucketStart(
    uint32 immediate_data_size,
    const void* cmd_data)
{
    const cmd::GetBucketStart& args = *static_cast<const cmd::GetBucketStart*>(cmd_data);
    uint32 bucket_id = args.bucket_id;
    uint32* result = GetSharedMemoryAs<uint32*>(
        args.result_memory_id, args.result_memory_offset, sizeof(*result));
    int32 data_memory_id = args.data_memory_id;
    uint32 data_memory_offset = args.data_memory_offset;
    uint32 data_memory_size = args.data_memory_size;
    uint8* data = NULL;
    if (data_memory_size != 0 || data_memory_id != 0 || data_memory_offset != 0) {
        data = GetSharedMemoryAs<uint8*>(
            args.data_memory_id, args.data_memory_offset, args.data_memory_size);
        if (!data) {
            return error::kInvalidArguments;
        }
    }
    if (!result) {
        return error::kInvalidArguments;
    }
    // Check that the client initialized the result.
    if (*result != 0) {
        return error::kInvalidArguments;
    }
    Bucket* bucket = GetBucket(bucket_id);
    if (!bucket) {
        return error::kInvalidArguments;
    }
    uint32 bucket_size = bucket->size();
    *result = bucket_size;
    if (data) {
        uint32 size = std::min(data_memory_size, bucket_size);
        memcpy(data, bucket->GetData(0, size), size);
    }
    return error::kNoError;
}

error::Error CommonDecoder::HandleGetBucketData(
    uint32 immediate_data_size,
    const void* cmd_data)
{
    const cmd::GetBucketData& args = *static_cast<const cmd::GetBucketData*>(cmd_data);
    uint32 bucket_id = args.bucket_id;
    uint32 offset = args.offset;
    uint32 size = args.size;
    void* data = GetSharedMemoryAs<void*>(
        args.shared_memory_id, args.shared_memory_offset, size);
    if (!data) {
        return error::kInvalidArguments;
    }
    Bucket* bucket = GetBucket(bucket_id);
    if (!bucket) {
        return error::kInvalidArguments;
    }
    const void* src = bucket->GetData(offset, size);
    if (!src) {
        return error::kInvalidArguments;
    }
    memcpy(data, src, size);
    return error::kNoError;
}

} // namespace gpu
